Integration request from AIML API - #7461
Conversation
aimlapi.com is an OpenAI-wire-compatible aggregator: one key fronts ~900 models. Modelled on the AIHubMix integration, which is the closest existing case here — same wire shape, same "aggregator with a fixed attribution header" problem. Attribution follows AIHubMix's own reasoning verbatim: every outbound call point funnels through `aimlapiHeaders()` instead of hand-building `Authorization` inline, so "every aimlapi.com request carries our attribution" stays enforceable in one place rather than re-derived per call site. aimlapi.com wants X-AIMLAPI-Source + X-AIMLAPI-Partner-ID on everything it serves, and a missing header is silent — the request succeeds, it just serves untagged. The daemon gets a dedicated /api/proxy/aimlapi/stream rather than a hostname branch inside the generic OpenAI route. Two reasons: the pair then rides unconditionally instead of depending on a hostname match, and it keeps the picker tab -> daemon log line -> upstream call chain readable, which is the same argument the AIHubMix client documents. It also keeps the pair out of the browser bundle, where a user could strip or forge it. Chat only. Media generation (the image/video/speech tool loop AIHubMix carries) is deliberately out of scope for this change. Typecheck pending: the workspace install had not finished when this was committed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Completes the previous commit. `ApiProtocol` is exhaustively mapped in several `Record<ApiProtocol, …>` tables, so adding the union member is only half the change — the compiler names every table that still has to answer for it, and each one is a real user-visible surface: - suggested models: the flagships aimlapi.com marks hottest, so the BYOK dropdown opens on something useful rather than empty - fast model: gemini-3.6-flash, for the memory extractor's auto pill - protocol tab, label, key placeholder, and the console link that sends users to https://aimlapi.com/app/keys - `ConnectionTestProtocol` and `MemoryExtractionProvider` in the contracts package, so the key smoke test and the memory extractor accept it Verified: typecheck clean in all three packages — daemon, web, contracts. Note for anyone repeating this: the daemon resolves `@open-design/contracts` through `dist`, so a contracts source edit does nothing until that package is rebuilt. The daemon's postinstall runs its own tsc, which is where the two contract mismatches surfaced. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both ids resolve, but openai/gpt-5.6-terra is the intended alias for the suggested-model default; -pro is a separate, heavier SKU. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add aimlapi.com as a BYOK chat provider
The protocol tab was appended, putting it last in the picker while every other table in the file already lists it first alphabetically. Moved to the front so the ordering is consistent with the labels, placeholders and console-link maps. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
List aimlapi.com first among providers
The 'aimlapi' protocol is already fully wired natively (state/apiProtocols.ts: its own tab, suggested models, default model, labels, and a dedicated providers/aimlapi-compatible.ts handler) — but KNOWN_PROVIDERS had no entry for it, so defaultApiProtocolConfig()/switchApiProtocolConfig() couldn't resolve a base URL when the aimlapi.com tab was selected in Settings: the Base URL field fell back to empty/placeholder instead of prefilling https://api.aimlapi.com/v1. Adds that entry with protocol: 'aimlapi' (matching the existing tab id) and a preferredModels list mirrored from SUGGESTED_MODELS_BY_PROTOCOL.aimlapi so the two stay in sync.
fix: register aimlapi.com in KNOWN_PROVIDERS with the correct protocol
Registering aimlapi.com in Settings (KNOWN_PROVIDERS/BYOK_PROVIDER_PRESET_SPECS) and daemon model listing wasn't enough to actually start a chat: the run preflight in ProjectView.tsx and the daemon's OpenCode provider mapper both enumerate supported BYOK protocols explicitly, and 'aimlapi' was missing from both lists plus the shared ByokChatProtocol contract type. That made every run treated as an unconfigured provider and bounced back to Settings even with a valid, saved, tested API key. - packages/contracts: add 'aimlapi' to ByokChatProtocol - apps/daemon/runtimes/byok-opencode: map aimlapi to @ai-sdk/openai-compatible - apps/web/ProjectView: recognize aimlapi in the BYOK run-preflight gate - apps/daemon/provider-models: aimlapi model listing (Load models) support - apps/web/state/config: surface aimlapi.com first in the BYOK provider grid
fix(aimlapi): recognize aimlapi.com as a supported BYOK chat protocol
Real BYOK chat runs through the OpenCode runtime, not the /api/proxy/*
routes: `streamMessage` — the web-provider dispatcher those routes exist
for — is called nowhere outside `providers/`, and ProjectView dispatches
`streamViaDaemon({ agentId: 'byok-opencode' })`. So the attribution pair
added with the provider only ever covered Test connection and model
discovery, while every actual inference request went out untagged.
That is the failure mode worth naming: nothing breaks. The chat works,
the connection test is green, and the traffic simply does not count.
`buildProviderEntry` hands `options` to the provider factory, which
forwards `headers` upstream, so the pair goes there. aimlapi.com now has
its own case rather than sharing the senseaudio/aihubmix one, since it is
the only one of the three that carries headers.
Also adds aimlapi to `usesAnthropicProxy`'s allow-list. Without it the
protocol falls through to the trailing baseUrl check and is treated as an
Anthropic proxy, which sends image attachments down the Anthropic path
for aimlapi.com users.
Test asserts the pair on the generated runtime config; confirmed it fails
when the headers are removed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Attribute the runtime that actually serves chat
|
Thanks @hugoaimlapi — the native provider path and daemon-side attribution direction are clear, and the AIHubMix prior-art makes the scope easy to follow. I’m adding the initial labels and reviewer routing now so the right folks can pick this up.
|
|
🧪 This PR has changes that need a manual QA pass before merge — please hold off self-merging for now; we’ll loop QA in once it’s merge-ready (and design/product have signed off, where applicable). |
|
Okay, thank you for the reply My Slack: hugo@aimlapi.com (and Telegram if it's more comfortable for you: @hug0the) |
mrcfps
left a comment
There was a problem hiding this comment.
Thanks @hugoaimlapi — the native BYOK path, the shared aimlapiHeaders() helper, and the later OpenCode attribution fix are a thoughtful, well-documented contribution.
Primary chat looks correctly wired: Settings → Test connection → byok-opencode with @ai-sdk/openai-compatible should work, and existing installs stay on Anthropic. A few follow-ups below on memory extraction, the unused proxy copy, and picker order — none of these should block the main chat path.
🔁 Powered by Looper · runner=reviewer · agent=omp · An autonomous AI dev team for your GitHub repos.
…oute Addresses the two review findings on nexu-io#7461. Memory extraction never learned the protocol. ProjectView forwards byokChatProvider.provider = 'aimlapi' on every BYOK turn, but memory-llm.ts PROVIDER_DEFAULTS had no aimlapi entry, and pickProvider() only enters the "same as chat" branch when PROVIDER_DEFAULTS[provider] is truthy. An aimlapi.com chat therefore fell through to ANTHROPIC/ OPENAI credentials or skipped extraction outright — the "I'm chatting with X but memory used Y" surprise that snapshot path exists to prevent. In practice it skipped: the new spec, run against the old code, records zero outbound calls. Mirrors the AIHubMix wiring: a PROVIDER_DEFAULTS entry, an env-key lookup, and the attribution pair spread from callOpenAI so the extractor's own request is not silently untagged. isExtractionProvider() also rejected a saved { provider: 'aimlapi' } override as invalid. It had drifted from MemoryExtractionProvider, which already admits senseaudio and aihubmix as well; all three reach the extractor through the OpenAI-compatible path, so the guard is now exhaustive over the type. /api/proxy/aimlapi/stream was a 159-line near-copy of the OpenAI stream route, including an Azure max_completion_tokens retry that is dead on api.aimlapi.com. BYOK chat goes through byok-opencode, not /api/proxy/*, and its only caller — apps/web/src/providers/aimlapi-compatible.ts — was never imported. Both are removed rather than left to drift from the handler they were copied from; aimlapiHeaders() stays on the connection test, model listing and byok-opencode, which are the live call sites.
mrcfps
left a comment
There was a problem hiding this comment.
Thanks @hugoaimlapi — the follow-up commit is a solid cleanup. Memory extraction now follows the aimlapi.com chat snapshot (PROVIDER_DEFAULTS + attribution + tests), and the unused /api/proxy/aimlapi/stream copy is gone.
One new blocker on this head: the new aimlapi-api transcript id is not in the BYOK OpenCode family allowlist, so multi-turn chat will drop prior context. Details are inline.
The earlier picker-order thread is still open on BYOK_PROVIDER_PRESET_SPECS / KNOWN_PROVIDERS / API_PROTOCOL_TABS; I left that on the existing thread rather than re-filing it.
🔁 Powered by Looper · runner=reviewer · agent=omp · An autonomous AI dev team for your GitHub repos.
Addresses the picker-order review finding on nexu-io#7461. The entries were prepended, which put a third-party aggregator ahead of Anthropic and OpenAI in Settings -> BYOK, the onboarding picker and the protocol tabs. Record insertion order is not a display contract, but API_PROTOCOL_TABS, KNOWN_PROVIDERS and BYOK_PROVIDER_PRESET_SPECS are — they are a product default for every new BYOK setup, not an alphabetical coincidence. All three now append the entry last, where AIHubMix already sits among the native tabs. The Record<ApiProtocol, ...> maps listed aimlapi first only because it was prepended to the ApiProtocol union; the union member and the map keys move with it so the whole set reads as appended rather than mixed. ConnectionTestProtocol and MemoryExtractionProvider get the same treatment (ByokChatProtocol was already appended). Pure reordering: no key, value or behaviour changes, and DEFAULT_CONFIG .apiProtocol stays 'anthropic', so no install changes vendor.
Addresses the transcript-family review finding on nexu-io#7461. ProjectView stamps each API-mode assistant turn with apiProtocolAgentId(config.apiProtocol) — 'aimlapi-api' for this tab — and then sends the run through streamViaDaemon({ agentId: 'byok-opencode' }). scopeHistoryToAgent walks back from the latest user turn and cuts the history at the first assistant turn whose agentId is not in the target's family; isSameTranscriptAgentFamily resolves that through the closed API_MODE_AGENT_IDS set, which 'aimlapi-api' had never joined. AIHubMix hit the same seam and is already a member. The effect is primary-path breakage for the feature this PR adds: from turn 2 on, an aimlapi.com BYOK chat ships only the latest user message and the model loses the conversation it just had. The new spec pins it — against the unfixed code the transcript collapses to exactly '## user\nmake the second step clearer'. Also labels 'aimlapi-api' in AGENT_LABELS so the chat header shows the provider instead of falling back to the raw agent id.
|
Thanks for the quick follow-up, @hugoaimlapi. The latest updates appear to address the Looper threads that were still open on the previous head, so at this point the main thing left is a refreshed reviewer pass on the current commit. |
mrcfps
left a comment
There was a problem hiding this comment.
@hugoaimlapi thank you for the careful follow-up commits — this is a thoughtful, well-scoped BYOK integration.
I re-reviewed head a90feec against the earlier threads. The previous issues are addressed on this head: memory extraction now follows the aimlapi.com chat snapshot (PROVIDER_DEFAULTS, attribution headers, and memory-aimlapi-byok.test.ts), the unused /api/proxy/aimlapi/stream copy is gone, the picker lists aimlapi.com after the first-party providers, and aimlapi-api is in the BYOK OpenCode transcript family with a regression test.
On the current diff, the main path looks correctly wired: Settings → aimlapi.com tab, Test connection and /v1/models go through aimlapiHeaders(), real chat goes through byok-opencode with @ai-sdk/openai-compatible plus the attribution pair, and existing installs stay on Anthropic. Nice work getting the AIHubMix-shaped seams in place without changing default behavior.
🔁 Powered by Looper · runner=reviewer · agent=omp · An autonomous AI dev team for your GitHub repos.






















































HUMAN:
Hi! I'm Hugo from aimlapi.com — an AI aggregator that gives access to 1000+ models in one API, trusted by 400k+ users.
We'd love to be available as a verified provider option inside OpenDesign — so we went ahead and did all the technical work on our side.
To build our partnership, we offer a 50/50 revenue share on all traffic from this integration. (P.S.: tracking starts as soon as this release goes live, so no earnings will be lost during setup)
My contacts: hugo@aimlapi.com (email / Slack), Telegram: @hug0the
AGENT:
Fixes #7459
Why
aimlapi.com is OpenAI-wire-compatible, so it already works through the generic "OpenAI" tab with a hand-typed base URL — but it isn't a provider: no tab, no key-console link, no curated model list, and users have to guess which of ~900 catalog ids to type. And unlike AIHubMix (
APP-Code), requests to aimlapi.com carry no attribution, so aimlapi.com can't tell OpenDesign traffic from anyone else's.This PR adds aimlapi.com as a native BYOK provider, following the AIHubMix pattern from #3583: one integration module (
apps/daemon/src/integrations/aimlapi.ts) owns the identity + outbound headers, and every daemon call to aimlapi.com — BYOK chat through the OpenCode runtime, the/api/proxy/aimlapi/streamroute, connection test and model discovery — funnels through it.What users will see
https://api.aimlapi.com/v1. The key field links toaimlapi.com/app/keys.openai/gpt-5.6-terra,anthropic/claude-sonnet-5,anthropic/claude-opus-5,google/gemini-3.6-flash,deepseek/deepseek-v4-pro,x-ai/grok-4-5,moonshot/kimi-k3); the live catalog loads fromGET /v1/modelsonce a key is entered.Surface area
KNOWN_PROVIDERS/BYOK_PROVIDER_PRESET_SPECSAIMLAPI_PARTNER_IDto override the compiled-in partner id (staging/test only; nothing required, noOD_*var)POST /api/proxy/aimlapi/stream;aimlapiadded toByokChatProtocol,ConnectionTestProtocol,MemoryExtractionProviderinpackages/contractsaimlapi.com)package.json/pnpm-lock.yamlunchangedScreenshots
To follow — Settings → BYOK with aimlapi.com selected, and a successful Test connection.
Bug fix verification
Not a bug fix — feature PR.
Validation
apps/daemon/tests/runtimes/byok-opencode.test.ts: the OpenCode provider config foraimlapiuses@ai-sdk/openai-compatiblewith the aimlapi.com base URL, and carries theX-AIMLAPI-Source/X-AIMLAPI-Partner-IDpair on real chat traffic.pnpm guard/pnpm typecheck/ package tests: relying on this PR's CI run; will fix anything it flags.api.aimlapi.com(smoke test,/v1/models, chat completions) should carryX-AIMLAPI-Source: agent/open-designandX-AIMLAPI-Partner-ID; on the aimlapi.com side the traffic shows up under the OpenDesign partner id.Notes
aihubmixHeaders()/APP-Code). Nothing in the agent loop or routing changes; previously saved settings load unchanged.